/* @flow */

import {hasOwn} from 'shared/util'
import {warn, hasSymbol} from '../util/index'
import {defineReactive, toggleObserving} from '../observer/index'

// 初始化provide，解析组件配置项上的provide对象，将其挂载到vm._provide属性上
export function initProvide(vm: Component) {
  // 取得配置项的provide
  const provide = vm.$options.provide

  if (provide) {
    // 配置项为函数，指向vm执行返回，不是则返回
    vm._provided = typeof provide === 'function'
      ? provide.call(vm)
      : provide
  }
}

// 初始化inject配置
// 1. 得到result[key] = val
// 2. 对结果数据进行响应式处理，代理每个key到vm实例
export function initInjections(vm: Component) {
  // 解析inject配置项，然后从祖代组件的配置中找到配置项每一个key对应的val，最后得到 result[key] = val 的结果
  const result = resolveInject(vm.$options.inject, vm)

  // 对result做数据响应式处理，也有代理inject配置中的每个key到vm实例的作用
  if (result) {
    toggleObserving(false)
    Object.keys(result).forEach(key => {
      /* istanbul ignore else */
      if (process.env.NODE_ENV !== 'production') {
        defineReactive(vm, key, result[key], () => {
          warn(
            `Avoid mutating an injected value directly since the changes will be ` +
            `overwritten whenever the provided component re-renders. ` +
            `injection being mutated: "${key}"`,
            vm
          )
        })
      } else {
        defineReactive(vm, key, result[key])
      }
    })
    toggleObserving(true)
  }
}

// 解析inject配置项，从祖代组件的provide配置中找到key对应的值，否则使用默认值
// 合并选项时，对组件配置对象做了标准化处理
/**
 * inject = {
  key: {
    from: provideKey,
    default: xx
  }
}
 *
 * */
export function resolveInject(inject: any, vm: Component): ?Object {
  if (inject) {
    // inject is :any because flow is not smart enough to figure out cached
    const result = Object.create(null)
    const keys = hasSymbol
      ? Reflect.ownKeys(inject)
      : Object.keys(inject)

    for (let i = 0; i < keys.length; i++) {
      const key = keys[i]
      // #6574 in case the inject object is observed...
      if (key === '__ob__') continue
      const provideKey = inject[key].from
      let source = vm
      // 遍历所有的组组件，直到根组件，找到对应的provide中对应的key的值，最后得到result[key] = provide[provideKey]
      while (source) {
        if (source._provided && hasOwn(source._provided, provideKey)) {
          result[key] = source._provided[provideKey]
          break
        }
        source = source.$parent
      }
      if (!source) {
        if ('default' in inject[key]) {
          const provideDefault = inject[key].default
          result[key] = typeof provideDefault === 'function'
            ? provideDefault.call(vm)
            : provideDefault
        } else if (process.env.NODE_ENV !== 'production') {
          warn(`Injection "${key}" not found`, vm)
        }
      }
    }
    return result
  }
}
